openair

The openair package (Carslaw and Ropkins 2012) allows you to retrieve and process data from air quality monitoring sites across the UK.

Install openair

The openair package can be downloaded from CRAN.

install.packages("openair")

Load openair

Once you have installed the openair package you can load it into the R session. We’ll also load the tidyverse package for data manipulation.

library(openair)
library(tidyverse)

Access site metadata

There are several air quality monitoring networks across the UK.

Let’s pull out site information on the Air Quality England network. We can use the importMeta() function to return site code, site name, coordinates and site type for each network orsource e.g. “aurn”, “aqe”, or “ni”. If we include the argument all = TRUE additional information on pollutants, local authority and status are supplied.

aqe <- importMeta(source = "aqe", all = TRUE)

Random sample of sites from AQE network

If we were interested in identifying active sites that measured PM2.5 at the roadside in Greater Manchester we could run.

aqe_gm <- filter(aqe, variable == "PM2.5", site_type == "Urban Traffic", end_date == "ongoing", local_authority %in% c("Bolton","Bury","Manchester","Oldham","Rochdale","Salford", "Stockport","Tameside","Trafford","Wigan"))

We could then map their spatial distribution.

distinct(aqe_gm, site, .keep_all = TRUE) %>% 
  sf::st_as_sf(coords = c(x = "longitude", y = "latitude"), crs = 4326) %>% 
  mapview::mapview(layer.name = "Roadside sites measuring PM2.5")

Import monitoring data

To retrieve data from one or more monitoring site we need to feed the importAQE() function with a site code and year. Let’s return all pollutants from the TRF2 monitoring site which is along the A56 in Stretford. We can also restrict the data returned to the last 10 years using the year argument.

trf2 <- importAQE(site = "TRF2", year = 2010:2022)

The data are returned at hourly intervals with measurements for pollutants including NO2 and PM10.

First 10 rows of monitoring data from Trafford A56 (TRF2)

Inspect monitoring data

Before we can visualise or analyse the data it is advisable to check it using the function summaryPlot. The plot in the left panel shows a time series of daily mean values for each continuous variable. Blue indicates the presence of data and red missing data. Summary statistics are also provided for each pollutant. The panel on the right is a histogram showing the distribution of values for each pollutant.

summaryPlot(trf2)

It looks like there is a lot of missing data for nitrogen oxides during 2013.

Visualise monitoring data

There are a wide variety of graphics in the openair package that are designed to facilitate interpretation of air quality data. As sites on the Air Quality England network don’t include wind speed and direction variables we will not be exploring these graphics, e.g. (windRose() and polarAnnulus()). The interested reader can learn more about these graphical types and when to use them in Carslaw & Davison (2023).

Time series plotting

The timePlot() function creates highly customisable time series plots of pollutant concentrations.

Let’s plot annual mean concentrations of NO2 and PM10 at the Trafford A56 (TRF2) monitoring site between 2010 and 2022. We’ll also normalise the data so that it is indexed from the start of 2010.

timePlot(trf2, pollutant = c("no2", "pm10"), avg.time = "year", normalise = "1/1/2010", group = TRUE, lwd = 2, ylim = c(0,120))

Annual concentrations of NO2 and PM10 have both declined since 2010. However, the rate of decline in PM10 is less pronounced than NO2 and seems to have plateaued since the mid-2010s.

Let’s look at seasonal patterns between NO2 and PM10 by selecting a narrower time frame with the selectByDate() function and use “month” as the averaging time. We’ll also allow the y-axis scales to be free and drop group = TRUE so that the plot is faceted by pollutant.

timePlot(selectByDate(trf2, year = 2020:2022), pollutant = c("no2", "pm10"), avg.time = "month", y.relation = "free", lwd = 2)

There is clear seasonal variation in concentrations of NO2 with peaks in the winter and troughs in the summer. PM10 concentrations seems to peak at springtime.

Temporal variations

The timeVariation() function plots temporal variation in pollutant concentrations. Four plots are generated: mean hour / day of the week, hour, day of the week, and month variation.

Let’s plot diurnal variation in PM10 concentrations at the Trafford A56 (TRF2) site.

timeVariation(trf2, pollutant = "pm10", local.tz = "Europe/London",
              ylab = "pm10 (ug/m3)")

It seems that PM10 concentrations increase sharply from 6am and only begin to decline after around 7pm. Weekday levels of PM10 are significantly higher than weekend concentrations with a peak on Thursdays. Levels of PM10 seem to be highest in March with lower concentrations during the summer months.

Smooth trend estimates

The smoothTrend() function calculates monthly mean concentrations of pollutants and fits a smooth line. The shading shows the estimated 95% confidence intervals for the fitted line.

smoothTrend(trf2, pollutant = "pm10", main = "Monthly mean deseasonalised PM10", deseason = TRUE)

Seasonal variation has been removed with deseason = TRUE and we can see that concentrations of PM10 have been largely flat since 2014.

Heat maps

The calenderPlot() function generates a calendar heatmap with daily mean concentrations for a chosen year.

calendarPlot(trf2, pollutant = "pm10", year = "2022", main = "PM10")

Here we can see that the highest daily mean concentrations of PM10 at the Trafford A56 site we on days during March and December 2022.

Air quality compliance

The openair package allows us to change the averaging period of the data retrieved from each monitoring site so that we compare pollutant concentrations with national air quality limits.

Pollutant metrics in the UK’s Air Quality Standards Regulations
Pollutant Averaging Period Limit
NO2 Annual mean 40 µg/m-3
NO2 Hourly mean 200 µg/m-3, 18 permitted exceedances each year
PM10 Annual mean 40 µg/m-3
PM10 24-hour mean 50 µg/m-3, with 35 permitted exceedances each year
PM2.5 Annual mean 20 µg/m-3

Nitrogen dioxide (NO2)

The annual mean concentration of NO2 at the Trafford A56 (TRF2) monitoring site in 2022 was 24 µg/m-3. This is below the annual mean concentration threshold of 40 µg/m-3.

importAQE(year = 2022, data_type = "annual", to_narrow = TRUE) %>% 
  filter(code == "TRF2", species == "no2") %>% 
  pull(value)

1-hour mean concentrations of NO2 exceeded 200 µg/m3 0 times at the Trafford A56 (TRF2) monitoring site in 2022. This is below the 1-hour mean threshold not to exceed 200 µg/m-3 more than 18 times in a single year.

importAQE(site = "TRF2", year = 2022, data_type = "hourly") %>%
  filter(no2 > 200) %>% 
  count()

Particulate Matter (PM)

The annual mean concentration of PM10 at the Trafford A56 (TRF2) monitoring site in 2022 was 16 µg/m3. This is below the annual mean concentration threshold of 40 µg/m-3.

importAQE(year = 2022, data_type = "annual", to_narrow = TRUE) %>% 
  filter(code == "TRF2", species == "pm10") %>% 
  pull(value)

24-hour mean concentrations of PM10 exceeded 50 µg/m-3 4 times at the Trafford A56 (TRF2) monitoring site in 2022. This is below the 24-hour mean threshold not to exceed 50 µg/m-3 more than 35 times in a single year.

importAQE(site = "TRF2", year = 2022, data_type = "daily") %>%
  filter(pm10 > 50) %>% 
  count()

We can use the calenderPlot() function to visualise these exceedances.

calendarPlot(trf2, pollutant = "pm10", year = "2022", 
             breaks = c(0, 50, 1000), 
             labels = c("Not exceeded", "Exceeded"),
             cols = c("#a6cee3", "#1f78b4"),
             main = "Days PM10 levels exceeded Air Quality Strategy Standards")

The Environmental Targets (Fine Particulate Matter) (England) Regulations 2023 require that by 2040 no monitoring station in England should exceed an annual mean PM2.5 concentration of 10 µg/m-3. Let’s see which monitoring stations exceeded those levels in 2022.

importAQE(year = 2022, data_type = "annual", to_narrow = TRUE) %>% 
  filter(species == "pm2.5", value > 10) %>%
  arrange(desc(value)) %>% 
  select(code, site, species, value) %>% 
  mutate(value = round(value, 1)) %>% 
  reactable()

References

Carslaw, David C., and Karl Ropkins. 2012. “Openair — an r Package for Air Quality Data Analysis” 27–28. https://doi.org/10.1016/j.envsoft.2011.09.008.